package com.desire.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 在访问volatile变量时不会执行加锁操作，因此也就不会使执行线程阻塞，
 * 因此volatile变量是一种比sychronized关键字更轻量级的同步机制。
 *
 * @author LittleDesire
 * @date 2022-02-09 周三 17:00
 * @since 17
 */
public class _53_volatile_synchronized {
    /**
     * volatile 关键字: 当多个线程进行操作共享数据时,可以保证内存中的数据是可见的;
     * 相较于 synchronized 是一种较为轻量级的同步策略
     * volatile 不具备"互斥性"
     * volatile 不能保证变量的"原子性"
     * <p>
     * Java 内存模型中的可见性、原子性和有序性。
     * 可见性：
     * 　　可见性是一种复杂的属性，因为可见性中的错误总是会违背我们的直觉。通常，我们无法确保执行读操作的线程能适时地
     * 看到其他线程写入的值，有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性，必须使用同步机制。
     * 　　可见性，是指线程之间的可见性，一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。
     * 另一个线程马上就能看到。比如：用volatile修饰的变量，就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序，
     * 即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题，volatile只能让被他修饰内容具有可见性，
     * 但不能保证它具有原子性。比如 volatile int a = 0；之后有一个操作 a++；这个变量a具有可见性，
     * 但是a++ 依然是一个非原子操作，也就是这个操作同样存在线程安全问题。
     * <p>
     * 　　         在 Java 中 volatile、synchronized 和 final 实现可见性。
     * 原子性：
     * 　　原子是世界上的最小单位，具有不可分割性。比如 a=0；（a非long和double类型） 这个操作是不可分割的，
     * 那么我们说这个操作时原子操作。再比如：a++； 这个操作实际是a = a + 1；是可分割的，所以他不是一个原子操作。
     * 非原子操作都会存在线程安全问题，需要我们使用同步技术（sychronized）来让它变成一个原子操作。一个操作是原子操作，
     * 那么我们称它具有原子性。java的concurrent包下提供了一些原子类，我们可以通过阅读API来了解这些原子类的用法。
     * 比如：AtomicInteger、AtomicLong、AtomicReference等。
     * <p>
     * 在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。
     * 有序性：
     * Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性，
     * volatile 是因为其本身包含“禁止指令重排序”的语义，synchronized 是由“一个变量在同一个时刻只允许一条线程对其
     * 进行 lock 操作”这条规则获得的，此规则决定了持有同一个对象锁的两个同步块只能串行执行。
     */


    /*    *//**
     * volatile 关键字，使一个变量在多个线程间可见
     * A B线程都用到一个变量，java默认是A线程中保留一份copy，这样如果B线程修改了该变量，则A线程未必知道
     * 使用volatile关键字，会让所有线程都会读到变量的修改值
     * <p>
     * 在下面的代码中，running是存在于堆内存的t对象中
     * 当线程t1开始运行的时候，会把running值从内存中读到t1线程的工作区，在运行过程中直接使用这个copy，并不会每次都去
     * 读取堆内存，这样，当主线程修改running的值之后，t1线程感知不到，所以不会停止运行
     * <p>
     * (ps:只要线程睡眠了,就会重新去堆内存中读取)
     *
     * <p>
     * 使用volatile，将会强制所有线程都去堆内存中读取running的值
     * <p>
     * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题，也就是说volatile不能替代synchronized
     *//*
    volatile boolean running = true; //对比一下有无volatile的情况下，整个程序运行结果的区别

    public static void main(String[] args) {
        _53_volatile_synchronized t = new _53_volatile_synchronized();
        new Thread(t::m, "t1").start();

        // 主线程 经过 4s 后将running的值改为false
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.running = false;
    }

    */
    /**
     * 该线程经过 2s 后结束
     *//*
    void m() {
        System.out.println("m start");
        while (running) {
            Thread.onSpinWait();
          *//*  try {
                // (ps:只要线程睡眠了,就会重新去堆内存中读取)
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*//*
        }
        System.out.println("m end!");
    }

    */


    //volatile int count = 0;

    //更高效
    AtomicInteger count = new AtomicInteger(0);

    /**
     * 对比上一个程序，可以用synchronized解决，synchronized可以保证可见性和原子性，volatile只能保证可见性
     */
    public static void main(String[] args) {
        _53_volatile_synchronized t = new _53_volatile_synchronized();

        // 定义了一个线程集合
        List<Thread> threads = new ArrayList<>();

        // 开启十个线程放到集合当中
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(t::m, "thread-" + i));
        }

        // 遍历集合中的线程 依次开启
        threads.forEach(Thread::start);

        //join保证每个线程独立运行   (只有当一个线程完成后才能进行下一个线程)避免脏读
        threads.forEach((o) -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        //最后输出count的值
        System.out.println(t.count);

    }

    /*synchronized*/ void m() {
        for (int i = 0; i < 10000; i++) {
            //count++;
            count.incrementAndGet();
        }
    }


}
